package de.tudarmstadt.fgbs.cafe.ui.akabahn;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.swing.JPanel;

import de.tudarmstadt.fgbs.cafe.structure.Bahnhof;
import de.tudarmstadt.fgbs.cafe.structure.Fahrplan;
import de.tudarmstadt.fgbs.cafe.structure.Wegpunkt;
import de.tudarmstadt.fgbs.cafe.structure.Zuglauf;
import de.tudarmstadt.fgbs.cafe.ui.akabahn.elements.ZwlLine;

/**
 * Class for painting a ZWL view of a given Fahrplan object
 * 
 * @author Christian Schlehuber
 * 
 */
public class ZWLPanel extends JPanel implements MouseListener,
	MouseMotionListener {

    private List<Bahnhof> stations;
    private List<ZWLBahnhof> zwlStations;
    private String startTime;
    private String endTime;
    private int xOffset = 80;
    private int yOffset = 80;
    private int shortLineLength = 10;
    private int shortLineLongLength = 15;
    private Fahrplan filteredFpl;
    private boolean alternateColors;
    private boolean editable;
    private boolean times;

    private int topMaxY = 0;
    private int bottomMaxY = 0;

    private Graphics2D g2d;
    private int preX, preY;
    boolean pressOut = false;
    private List<ZwlLine> shape;
    private ZwlLine draggedShape;

    // Direction of the line
    // true = left to right
    // false = right to left
    private boolean zwlLineDirectionLtR = true;
    // Type of line movement
    // 0 = full line up/down
    // -1 = left edge
    // 1 = right edge
    private short zwlLineMovement = 0;

    private static final int HIT_BOX_SIZE = 3;
    private static final int HIT_BOX_EDGES = 4;
    private static final int PROTECTION_AREA = 30;

    /**
     * 
     */
    private static final long serialVersionUID = -4019648634223983167L;

    // yPerElement = y per hour
    int yPerElement, xPerElement;
    int gridWidth, gridHeight;
    double yPerMinute;

    public ZWLPanel(List<Bahnhof> stationen, String startTime, String endTime,
	    Fahrplan fpl, boolean alternateColors, boolean editable,
	    boolean times) {

	shape = new ArrayList<ZwlLine>();
	this.endTime = endTime;
	this.startTime = startTime;
	this.stations = stationen;
	this.zwlStations = new ArrayList<ZWLBahnhof>();
	this.alternateColors = alternateColors;
	this.editable = editable;
	this.times = times;

	this.filteredFpl = filterFplZL(fpl, stationen);

	addMouseListener(this);
	addMouseMotionListener(this);
    }

    @Override
    public void paint(Graphics g) {
	shape.clear();
	System.out.println("paint called");
	Graphics2D g2 = (Graphics2D) g;
	g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
		RenderingHints.VALUE_ANTIALIAS_ON);
	Rectangle2D rect = new Rectangle2D.Double(0, 0, this.getWidth(),
		this.getHeight());

	if (!alternateColors)
	    g2.setColor(Color.black);
	else
	    g2.setColor(Color.white);
	g2.fill(rect);
	paintTimeGrid(g, this.startTime, this.endTime);
	paintStations(g);
	paintZuglaeufe(g);
    }

    private void paintStations(Graphics g) {
	int startX = this.xOffset + PROTECTION_AREA;
	xPerElement = computeStationSpace(stations, gridWidth - 20,
		PROTECTION_AREA);
	for (Iterator<Bahnhof> it = stations.iterator(); it.hasNext();) {
	    Bahnhof current = it.next();
	    ZWLBahnhof station = new ZWLBahnhof(current, startX, xPerElement);
	    zwlStations.add(station);
	    paintStation(g, station);
	    startX += xPerElement + PROTECTION_AREA;
	}
    }

    private void paintZuglaeufe(Graphics g) {

	printCurrentZL(filteredFpl.getZuglaeufe(), zwlStations, startTime,
		endTime, yPerElement, g);

    }

    private void paintTimeGrid(Graphics g, String startTime, String endTime) {
	if (!alternateColors)
	    g.setColor(Color.white);
	else
	    g.setColor(Color.black);
	gridWidth = this.getWidth() - (this.xOffset + this.xOffset / 2);
	gridHeight = this.getHeight() - (this.yOffset + this.yOffset / 2);
	int startMinutes = convertToMinutes(startTime);
	int endMinutes = convertToMinutes(endTime);
	yPerElement = computeSpace(startMinutes, endMinutes, gridHeight - 20);
	yPerMinute = (double) yPerElement / (double) 60;

	paintSurroundingBox(g, this.xOffset, this.yOffset,
		(this.getWidth() - this.xOffset / 2),
		(this.getHeight() - this.yOffset / 2));
	topMaxY = this.yOffset;
	bottomMaxY = this.getHeight() - this.yOffset / 2;
	int curPaintHeight = 10;
	int timeStep = 0;
	while (curPaintHeight < gridHeight) {
	    paintTimeLine(g, computeTimeString(startMinutes, timeStep),
		    this.yOffset + curPaintHeight);
	    curPaintHeight += yPerElement;
	    timeStep++;
	}

	// paintShortTimeLine(g, this.yOffset + 20, true);
	// paintShortTimeLine(g, this.yOffset + 40, false);
    }

    private void printCurrentZL(List<Zuglauf> zuglaeufe,
	    List<ZWLBahnhof> zwlStations, String startTime2, String endTime2,
	    int yPerTimeElement, Graphics g) {
	int startMinutes = convertToMinutes(startTime);
	for (Iterator<Zuglauf> it = zuglaeufe.iterator(); it.hasNext();) {
	    Zuglauf currentZL = it.next();
	    if (currentZL.passesStation(stations)) {
		// Find the according Wegpunkte and print them
		Wegpunkt lastWP = null;
		ZWLBahnhof lastZWLStation = null;
		for (Iterator<Wegpunkt> wpit = currentZL.getStationen()
			.iterator(); wpit.hasNext();) {
		    Wegpunkt currentWP = wpit.next();
		    ZWLBahnhof currentZWLStation = getAccordingZWLStation(currentWP
			    .getBahnhof());
		    if (currentZWLStation != null) {
			if (lastZWLStation != null) {
			    int startY = computeYCoordinate(
				    lastWP.getAbfahrt(), startMinutes,
				    yPerTimeElement);
			    int endY = computeYCoordinate(
				    currentWP.getAnkunft(), startMinutes,
				    yPerTimeElement);
			    paintZuglaufLine(currentZL.getNumber(),
				    lastZWLStation, lastWP.getRegelgleis(),
				    currentZWLStation,
				    currentWP.getRegelgleis(), startY, endY, g,
				    lastWP, currentWP, currentZL);
			}
			if (!currentWP.isDurchfahrt()
				&& !currentWP.getAnkunft().equals(
					currentWP.getAbfahrt())
				&& (currentWP.getAbfahrt().length() > 1)
				&& (currentWP.getAnkunft().length() > 1)) {
			    int startY = computeYCoordinate(
				    currentWP.getAnkunft(), startMinutes,
				    yPerTimeElement);
			    int endY = computeYCoordinate(
				    currentWP.getAbfahrt(), startMinutes,
				    yPerTimeElement);
			    paintAufenthaltLine(currentZWLStation,
				    currentWP.getRegelgleis(), startY, endY, g,
				    currentZL);
			}
			lastWP = currentWP;
			lastZWLStation = currentZWLStation;
		    }
		}
	    } else {
		System.out.println("ZL " + currentZL.getNumber() + " ignored");
	    }
	}
    }

    private int computeYCoordinate(String currentTime, int startMinutes,
	    int yPerTimeElement) {
	if (currentTime != null && currentTime != "") {
	    int current = convertToMinutes(currentTime);
	    current = current - startMinutes;
	    current = (yPerTimeElement / 30) * current + (yOffset + 10);
	    return current;
	} else
	    return 0;
    }

    private ZWLBahnhof getAccordingZWLStation(Bahnhof bhf) {
	if (bhf != null) {
	    for (Iterator<ZWLBahnhof> it = zwlStations.iterator(); it.hasNext();) {
		ZWLBahnhof current = it.next();

		if (current.getAssocBahnhof().getKurzbez()
			.equals(bhf.getKurzbez()))
		    return current;
	    }
	} else {
	    System.out.println("Aufruf mit leerem BHF");
	}
	return null;
    }

    /**
     * 
     * @param zlNr
     * @param station1
     *            Von Bahnhof
     * @param startGleis
     * @param station2
     *            Zu Bahnhof
     * @param endGleis
     * @param currentWP
     * @param currentZL
     * @param currentWP2
     * @param startTime
     * @param endTime
     */
    private void paintZuglaufLine(String zlNr, ZWLBahnhof station1,
	    String startGleis, ZWLBahnhof station2, String endGleis,
	    int startY, int endY, Graphics g, Wegpunkt lastWP,
	    Wegpunkt currentWP, Zuglauf currentZL) {
	if (endY < topMaxY || startY > bottomMaxY) {
	    return;
	}

	int x1 = station1.getGleisX(startGleis);
	int x2 = station2.getGleisX(endGleis);
	int x3 = Math.min(x1, x2) + (Math.max(x1, x2) - Math.min(x1, x2)) / 2;
	int y3 = Math.min(startY, endY)
		+ (Math.max(startY, endY) - Math.min(startY, endY)) / 2;
	if (startY < topMaxY) {
	    int diffY = topMaxY - startY;
	    int diffYTotal = Math.abs(startY - endY);
	    int diffX = x2 - x1;
	    int XperY = diffX / diffYTotal;
	    x1 = x1 + (diffY * XperY);
	    startY = topMaxY;
	}
	if (endY > bottomMaxY) {
	    int diffY = endY - bottomMaxY;
	    int diffYTotal = Math.abs(startY - endY);
	    int diffX = x1 - x2;
	    int XperY = diffX / diffYTotal;
	    x2 = x2 + (diffY * XperY);
	    endY = bottomMaxY;
	}

	if (currentZL.isFern)
	    g.setColor(Color.RED);
	else if (currentZL.isRegio)
	    g.setColor(Color.GREEN);
	else
	    g.setColor(Color.BLUE);

	if (y3 < bottomMaxY)
	    g.drawString(zlNr, x3, y3);

	if (times) {
	    int x1time, x2time;
	    if (x1 < x2) {
		x1time = x1 - 40;
		x2time = x2 + 5;
	    } else {
		x1time = x1 + 5;
		x2time = x2 - 40;
	    }
	    g.drawString(lastWP.getAbfahrt(), x1time, startY);
	    g.drawString(currentWP.getAnkunft(), x2time, endY);
	}

	// ZwlLine line = new ZwlLine(x1, startY, x2, endY);
	// line.paint(g);
	ZwlLine line2d = new ZwlLine(x1, startY, x2, endY, currentZL,
		station1.getAssocBahnhof(), station2.getAssocBahnhof());
	shape.add(line2d);
	g2d = (Graphics2D) g;
	// g2.setStroke(new BasicStroke(10));
	g2d.draw(line2d);
	// g.drawLine(x1, startY, x2, endY);

	if (!alternateColors)
	    g.setColor(Color.pink);
	else
	    g.setColor(Color.blue);

    }

    private void paintAufenthaltLine(ZWLBahnhof station, String gleis,
	    int startY, int endY, Graphics g, Zuglauf currentZL) {
	if (startY > bottomMaxY || endY < topMaxY) {
	    return;
	}
	if (endY > bottomMaxY) {
	    endY = bottomMaxY;
	}
	if (startY < topMaxY)
	    startY = topMaxY;
	int x = station.getGleisX(gleis);

	if (currentZL.isFern)
	    g.setColor(Color.RED);
	else if (currentZL.isRegio)
	    g.setColor(Color.GREEN);
	else
	    g.setColor(Color.BLUE);

	// shape.add(new Line2D.Float(x, startY, x, endY));
	g.drawLine(x, startY, x, endY);
	if (this.editable) {
	    g.fillOval(x - 2, startY - 2, 4, 4);
	    g.fillOval(x - 2, endY - 2, 4, 4);
	}

	if (!alternateColors)
	    g.setColor(Color.pink);
	else
	    g.setColor(Color.blue);
    }

    private Fahrplan filterFplZL(Fahrplan fpl2, List<Bahnhof> stationen) {
	Fahrplan temp = new Fahrplan();
	temp.setName(fpl2.getName());
	List<Zuglauf> tempZL = new ArrayList<Zuglauf>();

	// Currently only filters for passed stations
	for (Iterator<Zuglauf> it = fpl2.getZuglaeufe().iterator(); it
		.hasNext();) {
	    Zuglauf current = it.next();
	    if (current.passesStation(stationen))
		tempZL.add(current);
	}

	System.out.println("Reduced from " + fpl2.getZuglaeufe().size()
		+ " ZLs to " + tempZL.size() + " ZLs");

	temp.setZuglaeufe(tempZL);
	return temp;
    }

    private void paintStation(Graphics g, ZWLBahnhof station) {
	Graphics2D g2 = (Graphics2D) g;
	if (!alternateColors)
	    g2.setColor(Color.white);
	else
	    g2.setColor(Color.black);
	g2.setStroke(new BasicStroke(2));
	boolean first = true;
	g2.drawString(station.getAssocBahnhof().getKurzbez(),
		station.getStartX(), this.yOffset - 35);
	boolean even = false;
	boolean upper = false;
	g.setFont(new Font("Arial", Font.PLAIN, 9));
	for (Iterator<String> it = station.getAssocBahnhof().getGleise()
		.iterator(); it.hasNext();) {
	    String gleis = it.next();

	    if (gleis.length() > 1) {
		upper = !upper;
	    }
	    int diff = 5;
	    int xdiff = 0;
	    if (upper) {
		diff = 15;
		xdiff = 3;
	    } else {
		diff = 5;
	    }
	    g2.drawString(gleis, station.getGleisX(gleis) - (2 + xdiff),
		    this.yOffset - diff);
	    g2.drawLine(station.getGleisX(gleis), this.yOffset,
		    station.getGleisX(gleis), this.getHeight()
			    - (this.yOffset / 2));
	    if (first) {
		g2.setColor(Color.gray);
		g2.setStroke(new BasicStroke(1));
		first = false;
	    }
	    even = !even;
	}
	g.setFont(new Font("Arial", Font.PLAIN, 12));
    }

    private int computeStationSpace(List<Bahnhof> stations2, int width,
	    int schutzzone) {
	int stationCount = stations2.size();
	schutzzone = schutzzone * (stationCount - 1);
	int widthPerElement = (width - schutzzone) / stationCount;
	return widthPerElement;
    }

    private String computeTimeString(int startMinutes, int timeStep) {
	int currentMinutes = startMinutes + timeStep * 30;
	int hours = currentMinutes / 60;
	int minutes = currentMinutes - hours * 60;
	String minutesString = "";
	if (minutes == 0)
	    minutesString = "00";
	else
	    minutesString = Integer.toString(minutes);
	return hours + ":" + minutesString;
    }

    private int computeSpace(int startMinutes, int endMinutes, int totalSpace) {
	int diff = endMinutes - startMinutes;
	int elements = diff / 30;
	return totalSpace / elements;
    }

    /**
     * Method for converting a string time (10:23) to a integer representation
     * in form of a minute timestamp
     * 
     * @param timeString
     *            String containig the time information
     * @return int consisting of the minute timstamp
     */
    private int convertToMinutes(String timeString) {
	int minutes = 0;
	String[] timeParts = timeString.split(":");
	if (timeParts.length > 1) {
	    minutes += Integer.parseInt(timeParts[1]);
	    minutes += Integer.parseInt(timeParts[0]) * 60;
	    return minutes;
	} else
	    return 0;

    }

    private void paintSurroundingBox(Graphics g, int x1, int y1, int x2, int y2) {
	if (!alternateColors)
	    g.setColor(Color.WHITE);
	else
	    g.setColor(Color.black);
	g.drawLine(x1, y1, x2, y1);
	g.drawLine(x1, y1, x1, y2);
	g.drawLine(x2, y1, x2, y2);
	g.drawLine(x1, y2, x2, y2);
    }

    /**
     * Method for printing the hotizontal time lines of the background
     * 
     * @param g
     *            Graphics element, which is used for painting
     * @param time
     *            String containing the time string to print out
     * @param ypos
     *            int y position, where the line has to be drawn
     */
    private void paintTimeLine(Graphics g, String time, int ypos) {
	g.setColor(Color.darkGray);
	g.drawString(time, this.xOffset / 2, ypos);
	g.drawLine(this.xOffset, ypos, (this.getWidth() - this.xOffset / 2),
		ypos);
    }

    private void paintShortTimeLine(Graphics g, int ypos, boolean longer) {
	g.setColor(Color.gray);
	int endx = this.xOffset;
	if (longer) {
	    endx += shortLineLongLength;
	} else {
	    endx += shortLineLength;
	}
	g.drawLine(this.xOffset, ypos, endx, ypos);
    }

    @Override
    public void mouseDragged(MouseEvent e) {
	if (!pressOut && editable) {
	    boolean fullMove = false;
	    if ((e.getModifiersEx() & InputEvent.CTRL_DOWN_MASK) == InputEvent.CTRL_DOWN_MASK)
		fullMove = true;
	    updateLocation(e, fullMove);
	}
    }

    @Override
    public void mouseMoved(MouseEvent e) {
    }

    @Override
    public void mouseClicked(MouseEvent e) {
    }

    @Override
    public void mouseEntered(MouseEvent e) {
    }

    @Override
    public void mouseExited(MouseEvent e) {
    }

    @Override
    public void mousePressed(MouseEvent e) {
	if (editable) {
	    boolean hit = false;
	    System.out.println("Event");
	    int x = e.getX();
	    int y = e.getY();

	    int boxX = x - HIT_BOX_SIZE / 2;
	    int boxY = y - HIT_BOX_SIZE / 2;

	    for (Iterator<ZwlLine> it = shape.iterator(); it.hasNext();) {
		ZwlLine current = it.next();
		// System.out.println("Event: "+e.getX()+" - "+e.getY() +
		// " <-> Line: "+current.getX1()+" - "+current.getY1());
		if (current.intersects(boxX, boxY, HIT_BOX_SIZE, HIT_BOX_SIZE)) {
		    System.out.println("Event123");
		    draggedShape = current;
		    preX = x;
		    preY = y;
		    if (current.x1 < current.x2)
			this.zwlLineDirectionLtR = true;
		    else
			this.zwlLineDirectionLtR = false;

		    if (Math.abs(current.x1 - preX) <= HIT_BOX_EDGES)
			this.zwlLineMovement = -1;
		    else if (Math.abs(current.x2 - preX) <= HIT_BOX_EDGES)
			this.zwlLineMovement = 1;
		    else
			this.zwlLineMovement = 0;

		    boolean fullMove = false;
		    if ((e.getModifiersEx() & InputEvent.CTRL_DOWN_MASK) == InputEvent.CTRL_DOWN_MASK)
			fullMove = true;

		    updateLocation(e, fullMove);
		    hit = true;
		    break;
		}
	    }
	    if (!hit)
		pressOut = true;
	}
    }

    @Override
    public void mouseReleased(MouseEvent e) {
	if (editable && draggedShape != null
		&& draggedShape.contains(e.getX(), e.getY())) {
	    boolean fullMove = false;
	    if ((e.getModifiersEx() & InputEvent.CTRL_DOWN_MASK) == InputEvent.CTRL_DOWN_MASK)
		fullMove = true;
	    updateLocation(e, fullMove);
	} else {
	    pressOut = false;
	}
    }

    /**
     * Method, which updates the location of the according shape Note, that the
     * update is performed indirectly by manipulating the Abfahrt and Ankunft of
     * the affected waypoints
     * 
     * @param e
     *            MouseEvent, which occurred
     * @param fullMove
     */
    private void updateLocation(MouseEvent e, boolean fullMove) {
	if (draggedShape != null) {
	    Zuglauf current = filteredFpl.getZuglauf(draggedShape.getZl()
		    .getNumber());
	    Bahnhof start = draggedShape.getStartBhf();
	    Bahnhof end = draggedShape.getEndBhf();
	    int offset = 0;
	    if (e.getY() != preY) {
		int yDiff = e.getY() - preY;
		offset = (int) (yDiff / yPerMinute);
		if (offset != 0)
		    preY = e.getY();
		System.out.println("eY: " + e.getY() + " preY: " + preY
			+ " off: " + offset + " yDiff: " + yDiff + " yPer: "
			+ yPerMinute);
	    }
	    if (offset != 0) {
		if (fullMove) {
		    filteredFpl.incrementWholeZuglauf(draggedShape.getZl()
			    .getNumber(), offset);
		    // current.incrementWholeZuglauf(offset);
		} else {
		    if (this.zwlLineMovement == 0) {
			current.incrementScheduleTime(start, end, offset);
		    } else if (this.zwlLineMovement == -1) {
			System.out.println("Left Edge klicked");
			if (this.zwlLineDirectionLtR) {
			    filteredFpl.incrementAbfahrtZuglauf(
				    current.getNumber(), start, offset);
			    // current.incrementAbfahrtOnly(start, offset);
			} else {
			    filteredFpl.incrementAbfahrtZuglauf(
				    current.getNumber(), start, offset);
			    // current.incrementAbfahrtOnly(start, offset);
			}
		    } else if (this.zwlLineMovement == 1) {
			System.out.println("Right Edge klicked");
			if (this.zwlLineDirectionLtR) {
			    current.incrementAnkunftOnly(end, offset);
			} else {
			    current.incrementAnkunftOnly(end, offset);
			}
		    }
		}
		repaint();
	    }
	}
    }
}
